#include "Ping.h"
#include "RawSocket.h"

struct PingProto proto_v4 = {proc_v4, send_v4, init_v4, NULL, NULL, 0, IPPROTO_ICMP};

#ifdef IPV6
struct proto_v6 = {proc_v6, send_v6, init_v6, NULL, NULL, 0, IPPROTO_ICMPV6};
#endif

int datalen = 56;
int Ping(const char* szdestip, int ntimeout, const char* szlocalifname);
int main(int argc, char** argv)
{
    int c;
    struct addrinfo *ai;
    char const *h;
    opterr = 0;
    while ((c=getopt(argc, argv, "v") != -1))
    {
        switch(c)
        {
            case 'v':
            verbose++;
            break;
            case '?':
            err_quit("unrecoginzed option :%c", c);
        }
    }
    if (optind != argc-1)
    {
        err_quit("usage : %s [-v] <hostname>", *argv);
    }
    host = argv[optind];

    pid = getpid() & 0xffff;
    nsent = 1;
    signal(SIGALRM, sig_alarm);
    
    ai = host_serv(host, NULL, 0, 0);
    printf("addrlen=%d\n", ai->ai_addrlen);
    h = sock_ntop(ai->ai_addr, ai->ai_addrlen);
    printf("ping %s (%s) :%d data bytes\n"
            ,ai->ai_canonname ? ai->ai_canonname : h, h, datalen);

    if (ai->ai_family == AF_INET)
    {
        pr = &proto_v4;
        printf("proto_v4 proto :%d\tpr proto :%d, IPPROTO_ICMP=%d\n", proto_v4.icmpproto, pr->icmpproto, IPPROTO_ICMP);
    }
    else
    {
        err_quit("unknown address family :%d", ai->ai_family);
    }

    pr->sasend = ai->ai_addr;
    pr->sarecv = (struct sockaddr*)malloc(ai->ai_addrlen);
    pr->salen = ai->ai_addrlen;
    
    if (pr->finit)
    {
        (*pr->finit)();
    }
    
    sig_alarm(SIGALRM);

    readloop();

    #if 0
    Ping(host, 3000, "enp2s0");
    #endif
    
    return 0;
}

void readloop(void)
{
    printf("readloop...\n");
    char recvbuf[BUFSIZE];
    while (1)
    {
        printf("begin to recieve.\n");
        int nRet = recvfrom(sockfd, recvbuf, BUFSIZE, 0, (struct sockaddr*)pr->sarecv, &pr->salen);
        printf("nRet = %d\n", nRet);
        if (nRet >= 56)
        {
            struct iphdr* iphdr = (struct iphdr*) recvbuf;
            struct icmp* icmp_pack = (struct icmp*)(recvbuf + (iphdr->ihl << 2));
            if (icmp_pack->icmp_type == ICMP_ECHOREPLY)
            {
                printf("success\n");
                alarm(1);
            }
            else
            {
                printf("icmp type :%d, code :%d\n", icmp_pack->icmp_type, icmp_pack->icmp_code);
            }
            
        }
    }
    
    
    // char recvbuf[BUFSIZE];
    // char controlbuf[BUFSIZE];
    // struct msghdr msg;
    // struct iovec iov;
    // ssize_t n;
    // struct timeval tval;

    // iov.iov_base = recvbuf;
    // iov.iov_len = sizeof(recvbuf);
    // msg.msg_name = pr->sarecv;
    // msg.msg_iov = &iov;
    // msg.msg_iovlen = 1;
    // msg.msg_control = controlbuf;
    // while (1)
    // {
    //     msg.msg_namelen = pr->salen;
    //     msg.msg_controllen = sizeof(controlbuf);
    //     printf("before recvmsg\n");
    //     n = recvmsg(sockfd, &msg, 0);
    //     printf("after recvmsg, n :%ld\n", n);
    //     if (n < 0)
    //     {
    //         if (errno == EINTR)
    //         {
    //             printf("system intr!\n");
    //             continue;
    //         }
    //         else
    //         {
    //             err_quit("recvmsg error! errno :%d\tmsg :%s", errno, strerror(errno));
    //         }
    //     }
    //     gettimeofday(&tval, NULL);
    //     (*pr->fproc)(recvbuf, n, &msg, &tval);
    // }
}



void tv_sub(struct timeval *out, struct timeval *in)
{
    if ((out->tv_usec -= in->tv_usec) < 0)
    {
        --out->tv_sec;
        out->tv_usec += 1000*1000;
    }
    out->tv_sec -= in->tv_sec;
}


void proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *currtv)
{
    printf("proc_v4 starting...\n");
    int hlen, icmplen;
    double rtt;
    struct ip *ip;
    struct icmp *icmp;
    struct timeval *sendtv;

    ip = (struct ip*) ptr;
    hlen = ip->ip_hl << 2; //length of IP header
    if (ip->ip_p != IPPROTO_ICMP)
    {
        printf("not icmp!\n");
        return;
    }

    icmp = (struct icmp*)(ptr + hlen);
    if ((icmplen = len - hlen) < 8)
    {
        printf("malformed packet\n");
        return ;    //malformed packet
    }
    
    if (icmp->icmp_type == ICMP_ECHOREPLY)
    {
        if (icmp->icmp_id != pid)
        {
            printf("pid error! icmp_id = %d, pid = %d\n", icmp->icmp_id, pid);
            return;
        }

        if (icmplen < 16)
        {
            printf("icmplen error!\n");
            return ;
        }

        sendtv = (struct timeval *)icmp->icmp_data;
        tv_sub(currtv, sendtv);
        rtt = currtv->tv_sec * 1000.0 + currtv->tv_usec / 1000.0;
        printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f msg\n"
                , icmplen
                , sock_ntop(pr->sarecv, pr->salen)
                , icmp->icmp_seq
                , ip->ip_ttl
                , rtt);
    }
    else if (verbose)
    {
        printf(" %d bytes from %s: type = %d, code = %d\n"
                , icmplen
                , sock_ntop(pr->sarecv, pr->salen)
                , icmp->icmp_type, icmp->icmp_code);
    }
    else
    {
        printf("icmp_code = %d, icmp_type = %d, ICMP_ECHOREPLY = %d\n", icmp->icmp_code, icmp->icmp_type, ICMP_ECHOREPLY);
    }
    alarm(1);
}


void sig_alarm(int signo)
{
    (*pr->fsend)();
    return;
}


void send_v4(void)
{
    printf("send_v4 starting\n");
    int len;
    struct icmp *icmp;

    memset(sendbuf, 0, sizeof(sendbuf));
    icmp = (struct icmp*) sendbuf;
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_id = htons(pid);
    icmp->icmp_seq = htons(nsent++) ;
    memset(icmp->icmp_data, 0xa5, datalen); //fill with patten
    gettimeofday((struct timeval*)icmp->icmp_data, NULL);

    len = 8 + datalen;
    icmp->icmp_cksum = htons(in_cksum((u_short*)icmp, len));
    printf("seq = %d\n", icmp->icmp_seq);
    ssize_t sendlen = sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
    printf("sendlen :%ld\tlen %d\n", sendlen, len);
}

uint16_t in_cksum(uint16_t *addr, int len)
{
    int nleft = len;
    uint32_t sum = 0;
    uint16_t *w = addr;
    uint16_t answer = 0;

    /**
     * @brief using a 32 bit accumulator (sum), 
     * we add sequential 16 bit words to it,
     * and at the end, fold back all the carry bits 
     * from the top 16 bits into the lower 16 bits.
     * 
     */
    while (nleft > 1)
    {
        sum += *w++;
        nleft -= 2;
    }

    if (nleft == 1)
    {
        *(unsigned char*)(&answer) = *(unsigned char*)w;
        sum += answer;
    }
    
    sum = (sum >> 16) + (sum & 0xfff); //add hi 16 to low 16
    sum += (sum >> 16);
    answer = ~sum;
    return answer;
}

void init_v4(void)
{
    printf("init_v4...\n");
    if((sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto)) < 0)
    {
        printf("socket fail! errno :%d\tmsg :%s\n", errno, strerror(errno));
        printf("family :%s\tprotocol type :%d\n", getfamilystr(pr->sasend->sa_family), pr->icmpproto);
        printf("IPPROTO_ICMP :%d\n", IPPROTO_ICMP);
        return ;
    }
    printf("create socket success :%d\tfamily :%d\tprotocol :%d\n", sockfd, pr->sasend->sa_family, pr->icmpproto);
    setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, "enp2s0", sizeof("enp2s0"));
    //setuid(getuid());//切换至当前用户（普通用户），放弃root特权，避免其他意外
    //int size = 60 * 1024;
    //setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
}

void init_v6(void)
{

}


void proc_v6(char* ptr, ssize_t len, struct msghdr *msg, struct timeval *currtv)
{

}

void send_v6(void)
{
    
}


/*
未完待续
2023/09/19
正常发送，但无响应。再另外执行系统的ping指令又能接收回显应答

*/
#include <fcntl.h>
int Ping(const char* szdestip, int ntimeout, const char* szlocalifname)
{
    printf("destip :%s\ttimeout :%d\tlocal ifname :%s\n", szdestip, ntimeout, szlocalifname);

/* 1. 创建原始套接字 */    
    int pingsock = create_icmp_socket();
    if (pingsock < 0)
    {
        printf("create socket error, sock :%d\n", pingsock);
        return pingsock;
    }
    printf("socket = %d\n", pingsock);

//2. 绑定本地网卡
    // int setRS = setsockopt(pingsock, SOL_SOCKET, SO_BINDTODEVICE, szlocalifname, strlen(szlocalifname)+1);
    // if (setRS != 0)
    // {
    //     perror("setsockopt to bind device error");
    //     close(pingsock);
    //     return setRS;
    // }
    
//3. 构建目的地址    
    struct sockaddr_in pingaddr;
    memset(&pingaddr, 0, sizeof(pingaddr));
    pingaddr.sin_family = AF_INET;
    inet_aton(szdestip, &pingaddr.sin_addr);

//4. 构建数据包
    char packet[128] = {0};
    int packetsize = sizeof(packet);
    struct icmp *pkt = (struct icmp*) packet;
    memset(pkt, 0, packetsize);
    pkt->icmp_type = ICMP_ECHO;/*请求类型 ICMP回显请求*/
    pkt->icmp_seq = htons(1);
    pkt->icmp_code = 0; //请求码
    pkt->icmp_id = htons(getpid());
    pkt->icmp_cksum = in_cksum((unsigned short*)pkt, packetsize);
    struct timeval *tval = (struct timeval*)pkt->icmp_data;
    gettimeofday(tval, NULL);
    printf("send pack, icmp type :%d, icmp code :%d\n", pkt->icmp_type, pkt->icmp_code);

//5. 发送数据包
    int sendbits = sendto(pingsock, packet, packetsize, 0, (struct sockaddr*)&pingaddr, sizeof(pingaddr));
    if (sendbits<0 || sendbits!=packetsize)
    {
        close(pingsock);
        perror("sendto error");
        return 1;
    }
    
//6. 等待响应
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(pingsock, &rfds);
    struct timeval tv;
    tv.tv_sec = ntimeout / 1000;
    tv.tv_usec = 10;//(ntimeout % 1000) * 1000;
    printf("sec = %u, usec = %u\n", tv.tv_sec, tv.tv_usec);
    int retval = select(pingsock + 1, &rfds, NULL, NULL, &tv);
    if (retval <= 0)
    {
        //超时或者其他异常
        printf("retval :%d\n", retval);
        perror("select error");
        close(pingsock);
        return retval==0 ? 1 : -1;
    }
    
//7. 接收
    struct sockaddr_in from;
    socklen_t fromsize = sizeof(from);
    int recvbits;
    memset(packet, 0, sizeof(packet));
    if ((recvbits=recvfrom(pingsock, packet, packetsize, 0, (struct sockaddr*)&from, &fromsize)) >= 56)
    {
//8. 解析ICMP包
        struct ip* iphdr = (struct ip*) packet;
        pkt = (struct icmp*)(packet + (iphdr->ip_hl << 2));//skip ip hdr
        if (pkt->icmp_type == ICMP_ECHOREPLY)
        {
            close(pingsock);
            printf("%s is alive\n", szdestip);
            return 0;
        }
        else
        {
            printf("recv pack, icmp type :%d, icmp code :%d\n", pkt->icmp_type, pkt->icmp_code);
            printf("ICMP_ECHOREPLY=%d, ICMP_ECHO=%d\n", ICMP_ECHOREPLY, ICMP_ECHO);
        }
    }
    else
    {
        printf("bits of received :%d\n", recvbits);
    }
    close(pingsock);
    printf("%s is dead\n", szdestip);
    return 0;
}